﻿using System;
using System.Diagnostics;

using BrightSword.Feber.Samples;
using BrightSword.SwissKnife;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Tests.BrightSword.Feber.TestClasses;

namespace Tests.BrightSword.Feber
{
    internal static class SlowCloneExtensions
    {
        public static T SlowClone<T>(this T source) where T : new()
        {
            var destination = new T();
            foreach (var pi in typeof (T).GetAllProperties())
            {
                pi.SetValue(destination,
                            pi.GetValue(source,
                                        null),
                            null);
            }

            return destination;
        }
    }

    [TestClass]
    public class CloneFactoryTests
    {
        [TestMethod]
        public void TestSingleTypeClone()
        {
            var source = new ClassA
                         {
                             Integer = 42,
                             DateTime = new DateTime(2009,
                                                     2,
                                                     7),
                             String = "Black Saturday"
                         };
            var destination = source.Clone();

            Assert.AreEqual(42,
                            destination.Integer);
            Assert.IsTrue(source.AllPropertiesAreEqualWith(destination));
        }

        [TestMethod]
        public void TestDerivedInterfaceClone()
        {
            var source = new ClassDerivedWithProperties
                         {
                             Integer = 42,
                             DateTime = new DateTime(2009,
                                                     2,
                                                     7),
                             String = "Black Saturday"
                         };
            var destination = source.Clone<IDerivedWithoutProperties, ClassDerivedWithProperties, IBaseWithProperties>();

            Assert.AreEqual(42,
                            destination.Integer);
            Assert.IsTrue(source.AllPropertiesAreEqualWith(destination));
        }

        [TestMethod]
        public void TestTwoTypeCloneWithRelatedTypes()
        {
            var source = new ClassA
                         {
                             Integer = 42,
                             DateTime = new DateTime(2009,
                                                     2,
                                                     7),
                             String = "Black Saturday"
                         };

            var destination = source.Clone<ClassA, ClassAPlusPlus>();

            Assert.IsInstanceOfType(destination,
                                    typeof (ClassAPlusPlus));

            Assert.AreEqual(42,
                            destination.Integer);
            Assert.IsTrue(source.AllPropertiesAreEqualWith(destination));
        }

        [TestMethod]
        public void TestTwoTypeCloneWithLookalikeTypes()
        {
            var source = new ClassA
                         {
                             Integer = 42,
                             DateTime = new DateTime(2009,
                                                     2,
                                                     7),
                             String = "Black Saturday"
                         };

            var destination = source.Clone<ClassA, ClassALookAlike>();

            Assert.IsInstanceOfType(destination,
                                    typeof (ClassALookAlike));
            Assert.AreEqual(42,
                            destination.Integer);
            Assert.AreEqual(source.Integer,
                            destination.Integer);
            Assert.AreEqual(source.String,
                            destination.String);
            Assert.AreEqual(default(Double),
                            destination.ExchangeRate);
        }

        [TestMethod]
        public void TestThreeTypeCloneWithInterface()
        {
            var source = new ClassALookAlike
                         {
                             Integer = 42,
                             String = "Black Saturday",
                             ExchangeRate = 7.3
                         };

            var destination = source.Clone<ClassALookAlike, ClassAPlusPlus, IClassAPlusPlus>();

            Assert.IsInstanceOfType(destination,
                                    typeof (ClassAPlusPlus));
            Assert.AreEqual(42,
                            destination.Integer);
            Assert.AreEqual(source.Integer,
                            destination.Integer);
            Assert.AreEqual(source.String,
                            destination.String);
            Assert.AreEqual(source.ExchangeRate,
                            destination.ExchangeRate);
            Assert.AreEqual(default(DateTime),
                            destination.DateTime);
        }

        [TestMethod]
        public void TestThreeTypeCloneWithLookalikeTypes()
        {
            var source = new ClassAPlusPlus
                         {
                             Integer = 42,
                             String = "Black Saturday",
                             ExchangeRate = 7.3
                         };

            var destination = source.Clone<ClassAPlusPlus, ClassALookAlike, ClassAMutant>();

            Assert.IsInstanceOfType(destination,
                                    typeof (ClassALookAlike));
            Assert.AreEqual(42,
                            destination.Integer);
            Assert.AreEqual(source.Integer,
                            destination.Integer);
            Assert.AreEqual(source.String,
                            destination.String);
            Assert.AreEqual(default(Double),
                            destination.ExchangeRate); // this property does not exist in ClassAMutant

            // the property 'DateTime' does not exist in ClassALookAlike
        }

        [TestMethod]
        public void TimeClone()
        {
// ReSharper disable UnusedVariable
            var foo = new SmallClass().Clone();
// ReSharper restore UnusedVariable

            const int count = 1000*1000;
            var input = new SmallClass[count];

            for (var i = 0;
                i < count;
                i++)
            {
                input[i] = new SmallClass();
            }

            var output = new SmallClass[count];
            var sw = new Stopwatch();
            sw.Start();
            for (var i = 0;
                i < count;
                i++)
            {
                output[i] = input[i].Clone();
            }
            sw.Stop();

            Trace.WriteLine(String.Format("Cloning {0} items took {1} ms",
                                          count,
                                          sw.ElapsedMilliseconds));

            output = new SmallClass[count];
            sw = new Stopwatch();
            sw.Start();
            for (var i = 0;
                i < count;
                i++)
            {
                output[i] = SmallClass.CopyFrom(input[i]);
            }
            sw.Stop();

            Trace.WriteLine(String.Format("Copying {0} items took {1} ms",
                                          count,
                                          sw.ElapsedMilliseconds));

            output = new SmallClass[count];
            sw = new Stopwatch();
            sw.Start();
            for (var i = 0;
                i < count;
                i++)
            {
                output[i] = input[i].SlowClone();
            }
            sw.Stop();

            Trace.WriteLine(String.Format("Slow Cloning {0} items took {1} ms",
                                          count,
                                          sw.ElapsedMilliseconds));
        }

        internal class SmallClass
        {
            public SmallClass()
            {
                Id = Guid.NewGuid();
            }

            public Guid Id { get; private set; }

            public static SmallClass CopyFrom(SmallClass source)
            {
                return new SmallClass
                       {
                           Id = source.Id
                       };
            }
        }
    }
}